﻿using Message;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Timers;
using UnityEngine;

/// <summary>
/// 连接状态
/// </summary>
public enum State
{
    None,

    /// <summary>
    /// 已连接
    /// </summary>
    Connected,

    /// <summary>
    /// 断开
    /// </summary>
    DisConnect,

    /// <summary>
    /// 连接超时
    /// </summary>
    TimeOut,

    /// <summary>
    /// 连接出错
    /// </summary>
    Error
};

/// <summary>
/// 网络管理
/// </summary>
public class NetManager : MonoBehaviour
{
    public static NetManager Instance;
    private Socket socket;
    private byte[] buffer;
    private readonly int size = 1024;
    private int protoId;
    private bool isReceiving = false;
    private List<byte> receiveCache = new List<byte>();
    private bool isSending = false;
    /// <summary>
    /// 发送消息的队列
    /// </summary>
    private Queue<byte[]> sendCache = new Queue<byte[]>();
    /// <summary>
    /// 阿里云服务器ip
    /// </summary>
    private string serverUrl = "game.ganghood.net.cn";
    //private string ip = "127.0.0.1";

    /// <summary>
    /// 端口号
    /// </summary>
    private int serverPort = 16650;

    /// <summary>
    /// 心跳频率 5000毫秒1次
    /// </summary>
    private static float hbRate = 5000;

    private long sendTime = 0;
    private long receiveTime = 0;
    private long lastTickTime = 0;

    /// <summary>
    /// 定时器
    /// </summary>
    Timer timer = new Timer(hbRate);

    public Action<State> OnConnect;

    public State state = State.None;

    public NetManager()
    {
        lastTickTime = GetTimeStamp();
        timer.Elapsed += HandleMainTimer;
        timer.AutoReset = true;
        timer.Enabled = true;

        buffer = new byte[size];
        receiveCache = new List<byte>();
        sendCache = new Queue<byte[]>();
        Instance = this;
        MsgManager.Instance.AddListener(typeof(S2CHeatBeat), OnHeratBeat);
    }

    public void Connect()
    {
        try
        {
            IPEndPoint remoteIP = GetIPEndPoint(serverUrl, serverPort);
            if (remoteIP == null)
            {
                state = State.Error;
                if (OnConnect != null)
                {
                    OnConnect(state);
                }
                return;
            }

            //socket
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

            //Connect
            IAsyncResult result = socket.BeginConnect(remoteIP, null, null);

            //设置超时时间为2秒
            result.AsyncWaitHandle.WaitOne(2000, true);

            //连接成功
            if (result.IsCompleted)
            {
                //BeginReceive
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, AsyncCallBack, buffer);

                //状态
                state = State.Connected;

                lastTickTime = GetTimeStamp();
                timer.Start();

                if (OnConnect != null)
                {
                    OnConnect(state);
                }
            }
            //超时
            else
            {
                state = State.TimeOut;
                if (OnConnect != null)
                {
                    OnConnect(state);
                }
                Close();
            }
        }
        catch (Exception e)
        {
            Log.Debug(e.ToString());
            state = State.Error;
            if (OnConnect != null)
            {
                OnConnect(state);
            }
            Close();
        }
    }

    /// <summary>
    /// 接收回调
    /// </summary>
    /// <param name="ar"></param>
    private void AsyncCallBack(IAsyncResult ar)
    {
        try
        {
            if (socket == null)
            {
                return;
            }
            if (!socket.Connected)
            {
                return;
            }
            int count = socket.EndReceive(ar);
            if (count > 0)
            {
                byte[] data = new byte[count];
                Buffer.BlockCopy(buffer, 0, data, 0, count);
                OnReceive(data);

                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, AsyncCallBack, buffer);
            }
            else
            {
                //包尺寸有问题，断线处理
                Log.Debug("包尺寸有问题，断线处理");
                state = State.DisConnect;
                if (OnConnect != null)
                {
                    OnConnect(state);
                }
                Close();
            }

        }
        catch (Exception e)
        {
            Log.Debug(e.ToString());
            state = State.Error;
            if (OnConnect != null)
            {
                OnConnect(state);
            }
            Close();
        }
    }

    /// <summary>
    /// 消息处理
    /// </summary>
    /// <param name="data"></param>
    private void OnReceive(byte[] data)
    {
        //将接收到的数据放入数据池中
        receiveCache.AddRange(data);
        //如果没在读数据
        if (!isReceiving)
        {
            isReceiving = true;
            ReadData();
        }
    }

    /// <summary>
    /// 读取数据
    /// </summary>
    private void ReadData()
    {
        byte[] data = NetCode.Decode(ref protoId, ref receiveCache);

        if (data != null)
        {
            Type protoType = ProtoDic.GetProtoTypeByProtoId(protoId);
            object tos = ProtoBuf.Serializer.Deserialize(protoType, new MemoryStream(data));
            MsgManager.Instance.SendMsg(protoType, tos);

            //尾递归，继续读取数据
            ReadData();
        }
        else
        {
            isReceiving = false;
        }
    }

    /// <summary>
    /// 发送
    /// </summary>
    /// <param name="obj"></param>
    public void Send(object obj)
    {
        if (state != State.Connected)
        {
            Debug.LogError("[Connection]还没链接就发送数据是不好的");
            return;
        }

        if (!ProtoDic.ContainProtoType(obj.GetType()))
        {
            Debug.LogError("未知协议号");
            return;
        }

        byte[] data = NetCode.Encode(obj);
        sendCache.Enqueue(data);
        Send();
    }

    byte[] sendData;
    /// <summary>
    /// 发送
    /// </summary>
    private void Send()
    {
        if (socket == null)
        {
            return;
        }

        if (!socket.Connected)
        {
            return;
        }

        try
        {
            if (sendCache.Count == 0)
            {
                isSending = false;
                return;
            }
            isSending = true;
            sendData = sendCache.Dequeue();
            //Log.Debug("data.Length:" + data.Length); 
            socket.BeginSend(sendData, 0, sendData.Length, SocketFlags.None, null, null);
            isSending = false;
            Send();
        }
        catch (Exception e)
        {
            Log.Debug(e.Message);
            Close();
        }
    }

    private void HandleMainTimer(object sender, ElapsedEventArgs e)
    {
        HeartBeat();
    }

    void HeartBeat()
    {
        if (state != State.Connected)
        {
            return;
        }
        long nowTime = GetTimeStamp();
        Log.Debug("收到心跳 lastTickTime:" + lastTickTime + " nowTime:" + nowTime);
        //12秒收不到心跳断线重连
        if (nowTime - lastTickTime > 12)
        {
            Debug.LogError("断线了！重连去吧！");
            //Close();
        }
        //心跳
        if (state == State.Connected)
        {
            C2SHeatBeat hb = new C2SHeatBeat();
            TimeSpan ts = new TimeSpan(DateTime.Now.Ticks);
            hb.time = (long)ts.TotalMilliseconds;
            sendTime = hb.time;
            Send(hb);
            Log.Debug("发送心跳:" + sendTime);
        }
    }

    void OnHeratBeat(object protocol)
    {
        lastTickTime = GetTimeStamp();
        S2CHeatBeat sthb = protocol as S2CHeatBeat;
        TimeSpan ts = new TimeSpan(DateTime.Now.Ticks);
        receiveTime = (long)ts.TotalMilliseconds;
        Log.Debug("正确的延迟：" + (receiveTime - sendTime) / 2);
    }

    /// <summary>
    /// 关闭连接
    /// </summary>
    /// <returns></returns>
    public void Close()
    {
        timer.Stop();
        if (socket != null)
        {
            try
            {
                if (socket.Connected)
                {
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();
                }
                Log.Debug("关闭socket");
            }
            catch (Exception e)
            {
                Log.Debug("关闭失败:" + e.Message);
            }

            socket = null;

            state = State.None;
        }
    }

    private long GetTimeStamp()
    {
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        return Convert.ToInt64(ts.TotalSeconds);
    }

    /// <summary>
    /// 描述
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    private string GetDesc(byte[] bytes)
    {
        string str = "";
        if (bytes == null) return str;
        for (int i = 0; i < bytes.Length; i++)
        {
            int b = bytes[i];
            str += b.ToString() + " ";
        }
        return str;
    }

    private IPEndPoint GetIPEndPoint(string serverUrl, int serverPort)
    {
        IPEndPoint iPEndPoint = null;
        //获取 DNS 主机地址
        //DNS服务器中查询与某个主机名关联的IP地址，如果hostNameOrAddress 是 IP 地址，则不查询DNS服务器直接返回此地址
        IPAddress[] addresses = null;
        try
        {
            addresses = Dns.GetHostAddresses(serverUrl);
            for(int i =0;i<addresses.Length;i++)
            {
                Log.Debug("ip:" + addresses[i]);
            }
        }
        catch (Exception excep)
        {
            Log.Error("DNS Error:" + excep.ToString());
            return iPEndPoint;
        }
        if (addresses == null || addresses.Length == 0)
        {
            Log.Error("addresses:" + addresses.ToString());
            return iPEndPoint;
        }
        iPEndPoint = new IPEndPoint(addresses[0], serverPort);
        return iPEndPoint;
    }

    private void OnApplicationPause(bool pause)
    {
        //Close();
    }

    private void OnApplicationFocus(bool focus)
    {
        //重新登录
    }

    /// <summary>
    /// 当应用程序退出或编辑器结束运行
    /// </summary>
    private void OnApplicationQuit()
    {
        Log.Debug("Close");
        Close();
        timer.Stop();
    }
}